iT邦幫忙

2024 iThome 鐵人賽

DAY 9
1
JavaScript

Signal API in Angular系列 第 9

Day 09 - Template-driven form和Signal執行 雙向NgMode綁定

  • 分享至 

  • xImage
  •  

Angular支援template-drive form和reactive form,但是,signal容易與template-driven form配合使用。在NgModel能夠與signal執行two-way binding之後,我更多地使用template-driven form。

今天,我想講一下NgModel的故事。 Angular團隊最初引入它是為了綁定到primitve或物件屬性 (object property)。現在,ngModel可以綁定到signals來讀取和寫回它們。

導入表格模組

NgModel可透過將FormsModule匯入獨立元件 (standalone component) 來使用。

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [FormsModule],
 template: ‘’,
})
export class App {}

最簡單的方法:將Primitve或物件屬性 (object property) 綁定到ngModel

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div>
     <p class="bold">NgModel a primitive</p>
     <p>
       <span>Num:</span><input type="number" [(ngModel)]="num" />
     </p>
     <p>
       <span>Num Property:</span><input type="number" [(ngModel)]="obj.num" />
     </p>
     <span>Output: </span><span>{{ num }}, {{ num + 2 }}, {{ obj.num }}, {{ obj.num + 2 }}</span>
     <hr />
   </div>
 `,
})
export class App {
 num = 1;
 obj = { num: 1 };
}

當我在2016年開始使用Angular 2時,開發人員只能分別將primitive和物件屬性 (object property)綁定到ngModel。在範例中,數字和數字類型的物件屬性是與ngModel的雙向綁定。 對於剛接觸Angular的人來說,[(ngModel]]="num"是雙向綁定 (two-way binding)ngModel的語法。當輸入欄位發生變化時,對應的變數也會改變。範本顯示最新值:numnum + 2obj.numobj.num + 2

Reactive way:將BehaviorSubject綁定到ngModel

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { BehaviorSubject, map } from 'rxjs';

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [FormsModule, AsyncPipe],
 template: `
   <div>
     <p class="bold">NgModel a Behavior Subject</p>
     <p>
       <span>Num:</span><input type="number" [ngModel]="numSub.getValue()" (ngModelChange)="numSub.next($event)" />
     </p>
     <span>Output: </span><span>{{ numSub.getValue() }}, {{ plus2$ | async }}</span>
     <hr />
   </div>
 `,
})
export class App {
 // bind a behaviorSubject to ngModel
 numSub = new BehaviorSubject(1);
 // create a new Observable that adds 2 to the new num
 plus2$ = this.numSub.pipe(map((x) => x + 2));
}

一段時間後,開發了一種反應式模式 (reactive pattern) 來將BehaviorSubject綁定到ngModel。當BehaviorSubject獲得新的表單值時,訂閱該subject的消費者將獲取該值並可以操縱它來派生新結果。 在範例中,輸入欄位顯示 1,因為numSub的初始值是透過[ngModel]=numSub傳遞的。當輸入欄位更新時, (ngModelChange)=numSub.next($even)會將數值傳送給subjectplus2$ Observable然後將值對應到value + 2。 最後,該組件匯入AsyncPipe並解析 (resolve) 範本中的plus2$

用Signal重寫BehaviorSubject範例

在Angular引入signal之後,我用signalcomputed signal重寫了BehaviorSubject範例。 正是因為

  • RxJS最適合非同步活動和串流
  • Signa最適合同步活動,更新輸入欄位是同步操作。

signal的程式碼更改:

之前使用 RxJS

numSub = new BehaviorSubject(1);
plus2$ = this.numSub.pipe(map((v) => v + 2));

有signal後

 // bind a signal to ngModel
 numBeforeV17 = signal(1);
 // derive a new read-only signal to show num + 2
 plus2BeforeV17 = computed(() => this.numBeforeV17() + 2);

之前使用 RxJS

<p>
     <span>Num:</span><input type="number" [ngModel]="numSub.getValue()" (ngModelChange)="numSub.next($event)" />
</p>
<span>Output: </span><span>{{ numSub.getValue() }}, {{ plus2$ | async }}</span>

有signal後

<p>
       <span>Num:</span><input type="number" [ngModel]="numBeforeV17()" (ngModelChange)="numBeforeV17.set($event)" />
</p>
<span>Output: </span><span>{{ numBeforeV17() }}, {{ plus2BeforeV17() }}</span>

此外,我可以從遷移過程中刪除AsynPipesignal版本看起來與RxJS版本類似,更改的動機可能不高。 我希望您在看到NgModelsignal的雙向綁定 (two-way binding) 後改變主意。

跟signal雙向NgMode綁定

在Angular 17.0.1中,ngModel終於支援signal了。 [(ngModel)]="signal name"是可行的,並且減少了範本中的樣板程式碼

Angular版本17.0.1之前

// bind a signal to ngModel
 numBeforeV17 = signal(1);
 // derive a new read-only signal to show num + 2
 plus2BeforeV17 = computed(() => this.numBeforeV17() + 2);

Angular版本17.0.1之後

 // bind a signal to ngModel
 numV17 = signal(1);
 // derive a new read-only signal to show num + 2
 plus2V17 = computed(() => this.numV17() + 2);

Angular版本17.0.1之前

<p>
   <span>Num:</span><input type="number" [ngModel]="numBeforeV17()" (ngModelChange)="numBeforeV17.set($event)" />
</p>
<span>Output: </span><span>{{ numBeforeV17() }}, {{ plus2BeforeV17() }}</span>

Angular版本17.0.1之後

<p>
   <span>Num:</span><input type="number" [(ngModel)]="numV17()" />
</p>
<span>Output: </span><span>{{ numV17() }}, {{ plus2V17() }}</span>

程式碼變更產生相同的結果,並且減少了ngModel的樣板程式碼。對我來說,我更喜歡將signal綁定到 ngModel,它會在更改時通知其他signalscomputed signals

結論:

  • Angular社群正在等待Signal與表單的整合。
  • NgModel促進signal與template-driven form的整合。
  • 將變數綁定到NgModel的不同技術
    • 使用[(ngModel)]語法將primitive或Object property綁定到表單控制元素。
    • 使用BehaviorSubject來追蹤HTML輸入值。 [ngModel]綁定到behaviorSubject.getValue(),並且(ngModelChange)將值傳送至behaviorSubject.next()
    • 在Angular 17.0.1之前,signal分別綁定到[ngMode](ngModelChange)
    • Angular 17.0.1及更高版本,signal可以按以下語法綁定到ngModel[(ngModel)]="<signal name>"

鐵人三賽第9天到此結束。

參考:

Stackblitz Demo:


上一篇
Day 08 - 避免root-level service和 toSignal中的memory leak
下一篇
Day 10 - Signal API - 我們可以在Effect中做什麼?
系列文
Signal API in Angular10
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言